{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# Using Function Call of ZhipuAI GLM API\n",
    "\n",
    "**This tutorial is available in English and is attached below the Chinese explanation**\n",
    "\n",
    "本代码旨在使用 GLM-4 完成简单的 Function Call 代码，通过一个查询天气的工具，让开发者清楚大模型是如何完成工具调用的任务的。\n",
    "\n",
    "This cookbook aims to use GLM-4 to complete a simple Function Call code. Through a tool for querying weather, it allows developers to understand how the large model completes the task of tool calling."
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "71c55dc5a09277b"
  },
  {
   "cell_type": "markdown",
   "source": [
    "首先，设定好调用模型的API key\n",
    "\n",
    "First, set the API key for calling the model"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "ffe8c1389340e246"
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "outputs": [],
   "source": [
    "import os\n",
    "from zhipuai import ZhipuAI\n",
    "\n",
    "os.environ[\"ZHIPUAI_API_KEY\"] = \"your api key\"\n",
    "client = ZhipuAI()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-03-01T11:13:55.975225Z",
     "start_time": "2024-03-01T11:13:55.535486Z"
    }
   },
   "id": "b6d23e0f93a4967c"
  },
  {
   "cell_type": "markdown",
   "source": [
    "首先，定义一个简单的工具，这个工具不一定要真实存在，但是你需要包含以下的内容：\n",
    "\n",
    "1. 工具的名字`name` ：这让模型返回调用工具的名称\n",
    "2. 模型的描述`description`: 对工具的描述\n",
    "3. 模型传入的参数`parameters`: 这里记录了工具参数的属性和内容。\n",
    "4. `required`: 通常配合`parameters`，指的是需要传入模型的参数的名称，在这里，只有`location`是需要传入的。\n",
    "\n",
    "这样就定义好了一个简单的的工具，用于查询地区的天气。\n",
    "\n",
    "First, define a simple tool. This tool does not necessarily need to exist, but you need to include the following:\n",
    "\n",
    "1. The name of the tool `name`: This allows the model to return the name of the calling tool\n",
    "2. Model description `description`: description of the tool\n",
    "3. Parameters passed in by the model: The attributes and contents of the tool parameters are recorded here.\n",
    "4. `required`: usually used with `parameters`, refers to the name of the parameters that need to be passed into the model. Here, only `location` needs to be passed in.\n",
    "\n",
    "This way defined a simple tool for querying the weather in a region.\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "ac8e4b4784a49c1e"
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "outputs": [],
   "source": [
    "tools = [\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_current_weather\",\n",
    "            \"description\": \"获取指定城市的天气信息\",\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"location\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"城市，如：北京\",\n",
    "                    },\n",
    "                    \"unit\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"enum\": [\"c\", \"f\"]\n",
    "                    },\n",
    "                },\n",
    "                \"required\": [\"location\"],\n",
    "            },\n",
    "        }\n",
    "    }\n",
    "]"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-03-01T11:13:55.981070Z",
     "start_time": "2024-03-01T11:13:55.975223Z"
    }
   },
   "id": "15c6d9390bdbf2dc"
  },
  {
   "cell_type": "markdown",
   "source": [
    "接着，我们就能将工具和信息组合成一个请求，并传入模型中获得回答，这里我们按照[官方教程](https://open.bigmodel.cn/dev/api#sse_example)的方式进行请求。\n",
    "\n",
    "Then, we can combine the tools and information into a request and pass it into the model to get the answer. Here we make the request according to the [official tutorial](https://open.bigmodel.cn/dev/api#sse_example) ."
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "767f2027b6f5937e"
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "outputs": [
    {
     "data": {
      "text/plain": "[CompletionChoice(index=0, finish_reason='tool_calls', message=CompletionMessage(content=None, role='assistant', tool_calls=[CompletionMessageToolCall(id='call_8434493403450401873', function=Function(arguments='{\"location\":\"北京\",\"unit\":\"c\"}', name='get_current_weather'), type='function')]))]"
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "messages = [{\"role\": \"user\", \"content\": \"今天北京天气如何？\"}]\n",
    "completion = client.chat.completions.create(\n",
    "    model=\"glm-4\",\n",
    "    messages=messages,\n",
    "    tools=tools,\n",
    "    tool_choice=\"auto\",\n",
    ")\n",
    "completion.choices"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-03-01T11:13:56.932054Z",
     "start_time": "2024-03-01T11:13:55.981613Z"
    }
   },
   "id": "6561543e9c14a3"
  },
  {
   "cell_type": "markdown",
   "source": [
    "模型返回的内容中，有效的部分其实是关于 工具调用传参的部分，观察到模型传入的参数为\n",
    "```\n",
    "{   \n",
    "    \"location\":\"北京\",\n",
    "    \"unit\":\"celsius\"\n",
    "}\n",
    "```\n",
    "调用工具的名称为 `get_current_weather`\n",
    "\n",
    "但是，这还不能让我们直接获取结果，因此，还需要通过工程化的方式来真正的调用这个模型，并执行这个调用请求，返回给大模型。\n",
    "\n",
    "The valid part of the content returned by the model is actually the parameter part passed by the tool call. It can be seen that the parameters passed in the model are\n",
    "````\n",
    "{\n",
    "    \"location\":\"Beijing\",\n",
    "    \"Unit\": \"Celsius\"\n",
    "}\n",
    "````\n",
    "The name of the calling tool is `get_current_weather`\n",
    "\n",
    "However, this does not allow us to obtain the results directly. Therefore, we need to actually call the model through the project, execute the call request, and return it to the large model."
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "812a5291d34dbbb8"
  },
  {
   "cell_type": "markdown",
   "source": [
    "为了能够顺利展示工具的执行，我先对 `get_current_weather` 的代码用python进行简单实现。我使用 [心知天气](https://www.seniverse.com/) 的API并简单书写了一个请求及时天气的脚本。你需要使用 API 私钥 来填写到  `xinzhi_api_key` 中。\n",
    "\n",
    "In order to be able to smoothly demonstrate the execution of the tool, I first simply implement the code of `get_current_weather` in python. I used the API of [Xinzhiwei](https://www.seniverse.com/) and simply wrote a script to request real-time weather. You need to use the API private key to fill in `xinzhi_api_key`."
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "58857518229342fa"
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/zr/Code/glm-cookbook/venv/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "xinzhi_api_key = \"your xinzhi api key\"\n",
    "\n",
    "\n",
    "def get_current_weather(location: str, unit: str):\n",
    "    url = f\"https://api.seniverse.com/v3/weather/now.json?key={xinzhi_api_key}&location={location}&language=zh-Hans&unit={unit}\"\n",
    "    response = requests.get(url)\n",
    "    if response.status_code == 200:\n",
    "        data = response.json()\n",
    "        weather = {\n",
    "            \"temperature\": data[\"results\"][0][\"now\"][\"temperature\"],\n",
    "            \"description\": data[\"results\"][0][\"now\"][\"text\"],\n",
    "        }\n",
    "        return weather\n",
    "    else:\n",
    "        raise Exception(\n",
    "            f\"Failed to retrieve weather: {response.status_code}\")\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-03-01T11:13:56.979654Z",
     "start_time": "2024-03-01T11:13:56.933335Z"
    }
   },
   "id": "4447433ec2585b9f"
  },
  {
   "cell_type": "markdown",
   "source": [
    "接着，通过简单的 Python 脚本将模型的工具调用请求执行并返回模型相关结果。\n",
    "\n",
    "Next, a simple Python script is used to execute the model's tool call request and return model-related results."
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "467bcd1ebfcd3e0f"
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "outputs": [
    {
     "data": {
      "text/plain": "Completion(model='glm-4', created=1709291639, choices=[CompletionChoice(index=0, finish_reason='stop', message=CompletionMessage(content='今天北京的天气是晴天，温度为3摄氏度。请注意保暖，因为气温较低。', role='assistant', tool_calls=None))], request_id='8434493437810171341', id='8434493437810171341', usage=CompletionUsage(prompt_tokens=24, completion_tokens=21, total_tokens=45))"
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "\n",
    "def extract_function_and_execute(llm_output, messages):\n",
    "    name = llm_output.choices[0].message.tool_calls[0].function.name\n",
    "    params = json.loads(llm_output.choices[0].message.tool_calls[0].function.arguments)\n",
    "    function_to_call = globals().get(name)\n",
    "    if not function_to_call:\n",
    "        raise ValueError(f\"Function '{name}' not found\")\n",
    "\n",
    "    messages.append(\n",
    "        {\n",
    "            \"role\": \"tool\",\n",
    "            \"content\": str(function_to_call(**params))\n",
    "        }\n",
    "    )\n",
    "    return messages\n",
    "\n",
    "\n",
    "messages = [{\"role\": \"user\", \"content\": \"今天北京天气如何？\"}]\n",
    "\n",
    "completion = client.chat.completions.create(\n",
    "    model=\"glm-4\",\n",
    "    messages=messages,\n",
    "    tools=tools,\n",
    "    tool_choice=\"auto\",\n",
    ")\n",
    "extract_function_and_execute(llm_output=completion, messages=messages)\n",
    "\n",
    "final_answer = client.chat.completions.create(\n",
    "    model=\"glm-4\",\n",
    "    messages=messages,\n",
    "    tool_choice=\"auto\",\n",
    ")\n",
    "final_answer"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-03-01T11:13:59.086065Z",
     "start_time": "2024-03-01T11:13:56.981906Z"
    }
   },
   "id": "58768f7473e2b1ca"
  },
  {
   "cell_type": "markdown",
   "source": [
    "在这段代码中，工具被执行后又以 `tool` 的身份传入到大模型中，大模型会根据工具的返回给予用户回答。因此，工具调用的基本顺序是：\n",
    "\n",
    "用户请求（和工具） -> 模型输出调用工具的信息和参数 -> 工具执行 -> 结果返回模型 -> 模型总结并回答用户\n",
    "\n",
    "In this code, after the tool is executed, it is passed to the large model in the character of `tool`, and the large model will give the user an answer based on the return of the tool. Therefore, the basic sequence of tool calls is:\n",
    "\n",
    "User request (and tool) -> model outputs information and parameters calling the tool -> tool execution -> results returned to model -> model summarizes and answers to user"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "c01842a12ebb68d1"
  },
  {
   "cell_type": "markdown",
   "source": [],
   "metadata": {
    "collapsed": false
   },
   "id": "d1080d899909339"
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Close Web Search of ZhipuAI GLM API\n",
    "\n",
    "GLM-4 API 提供了一个简单的工具，用于搜索网络上的信息。这个工具的调用方式和上面的 Function Call 类似，但是需要传入的参数和返回的结果是不同的。\n",
    "\n",
    "由于在默认调用的时候，会默认开启网络搜索，有两种方式更比较灵活的控制网络搜索的开启和关闭。\n",
    "\n",
    "1. 调用了其他工具，如果调用其他工具或者 Retrival ，则联网功能会被关闭\n",
    "2. 通过 `enable` 参数来控制网络搜索的开启和关闭，默认开启，在本demo中提供了一种关闭的办法\n",
    "\n",
    "**注意：目前GLM-4 API 搜索能力并不是很强大，在很多场景中优化不佳，因此在实际使用中，建议使用专业的搜索引擎API**\n",
    "\n",
    "\n",
    "The GLM-4 API provides a simple tool for searching information on the web. The calling method of this tool is similar to the function call above, but the parameters that need to be determined and the returned results are different.\n",
    "\n",
    "Since network search will be enabled by default when called by default, there are two ways to more flexibly control the opening and closing of network search.\n",
    "\n",
    "1. Other tools are called. If other tools or searches are called, the networking function will be turned off.\n",
    "2. Use the `enable` parameter to control the opening and closing of network search. It is enabled by default. This demo provides a method of closing it.\n",
    "\n",
    "**Note: The current GLM-4 API search capability is not very strong and is not well optimized in many scenarios. In actual use, it is recommended to use a professional search engine API**\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "5dd29a7748969474"
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [
    {
     "data": {
      "text/plain": "Completion(model='glm-4', created=1709291646, choices=[CompletionChoice(index=0, finish_reason='stop', message=CompletionMessage(content='截至我知识的更新时间点（2023年初），最新的“全球独角兽企业100强”榜单尚未公布。通常这类榜单由知名的市场研究机构、财经媒体或投资机构发布，如胡润研究院、CB Insights等，它们会根据企业的估值、增长速度、行业影响力等指标进行评选。\\n\\n如果您想了解最新的全球独角兽企业排名，建议关注以下几家机构的发布：\\n\\n1. 胡润研究院发布的“全球独角兽榜”；\\n2. CB Insights的“全球独角兽企业排名”；\\n3. 《财富》杂志的“全球独角兽企业100强”。\\n\\n一般到每年的中后期，这些机构会发布当年的最新排名。对于2022年及以前的排名，这些机构网站上通常可以找到相关的历史数据。\\n\\n需要注意的是，独角兽企业的名单和排名会随着市场的变化、企业估值的变动以及新投资的注入等因素发生变化，因此最新的排名可能会有所不同。如果您需要获取最新的信息，建议直接访问这些机构的官方网站或通过其它权威财经新闻渠道获取。', role='assistant', tool_calls=None))], request_id='8434489383360966726', id='8434489383360966726', usage=CompletionUsage(prompt_tokens=16, completion_tokens=217, total_tokens=233))"
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tools_web_search = [\n",
    "    {\n",
    "        \"type\": \"web_search\",\n",
    "        \"web_search\": {\n",
    "            \"enable\": False,\n",
    "        }\n",
    "    }\n",
    "\n",
    "]\n",
    "\n",
    "messages = [{\"role\": \"user\", \"content\": \"2023年100强独角兽企业有哪些？\"}]\n",
    "\n",
    "completion_without_web = client.chat.completions.create(\n",
    "    model=\"glm-4\",\n",
    "    messages=messages,\n",
    "    tools=tools_web_search,\n",
    ")\n",
    "completion_without_web"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-03-01T11:14:05.925170Z",
     "start_time": "2024-03-01T11:13:59.092400Z"
    }
   },
   "id": "a44865bad126aac1"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
